#include <string.h>
#include <stdio.h>
#include <elf.h>
#include <common.h>
#ifdef CONFIG_FTRACE
#define SECTION_HEADER_BUF_NUM 20
#define SYMBOL_TABLE_BUF_NUM 50
#define STR_TABLE_BUF_NUM 500
//elv_file is the name of elf_file should be init to NULL before be set
static Elf64_Ehdr elf_header; 
static Elf64_Shdr section_headers[SECTION_HEADER_BUF_NUM];
static Elf64_Sym  symbol_tables[SYMBOL_TABLE_BUF_NUM];
static int symbol_table_num;
char str_table[STR_TABLE_BUF_NUM];
//load_elf file
//used in monitor.c when ftrace is open
//load_information into buf
void load_elf(char *elf_file){
	if(elf_file == NULL){
		Log("No elf is given.");
		assert(elf_file);//"ftrace is set buf elf_file is not given");
	}
	FILE *fp = fopen(elf_file,"rb");
	assert(fp);//"Can not open '%s'",elf_file);
	fseek(fp,0,SEEK_END);
	long size = ftell(fp);
	Log("The elf is %s, size = %ld",elf_file,size);
	fseek(fp,0,SEEK_SET);
	//read elf_header;
	int ret = fread((char*)&elf_header,sizeof(elf_header),1,fp);
	assert(ret == 1);
	//read section_headers
	assert(SECTION_HEADER_BUF_NUM >= elf_header.e_shnum);//,"section header buf is too short");
	fseek(fp,elf_header.e_shoff,SEEK_SET);
	ret = fread((char*)section_headers,elf_header.e_shnum*elf_header.e_shentsize,1,fp);
	assert(ret == 1);
	//read symbol_table and string table
	int find_symbol = 0;
	int find_str = 0;
	for(int i=0;i<elf_header.e_shnum;i++){
		if(section_headers[i].sh_type == SHT_SYMTAB){
			symbol_table_num = section_headers[i].sh_size/section_headers[i].sh_entsize;
			assert(SYMBOL_TABLE_BUF_NUM >= symbol_table_num);//,"symbol_table_buf is too short");
			fseek(fp,section_headers[i].sh_offset,SEEK_SET);
			ret = fread((char*)symbol_tables,section_headers[i].sh_size,1,fp);
			assert(ret == 1);
			find_symbol = 1;
		}
		if(section_headers[i].sh_type == SHT_STRTAB){
			assert(STR_TABLE_BUF_NUM >= section_headers[i].sh_size);//,"str_table_buf is too short");
			fseek(fp,section_headers[i].sh_offset,SEEK_SET);
			ret = fread(str_table,section_headers[i].sh_size,1,fp);
			assert(ret == 1);
			find_str = 1;
		}
		if(find_str ==1 && find_symbol == 1){break;}
	}	
	assert(find_symbol == 1 && find_str ==1);//,"not find symtab or strtab in elf file");
	//for(int i = 0;i<symbol_table_num;i++){printf("%lx\n",symbol_tables[i].st_value);}
	fclose(fp);
}

//usd for ftrace
//used in inst.c in jal and jalr when the CONFIG_FTRACE is defined
//this function can Determine whether is a function call or a return according the dest and dnpc
//it will generate the Log
static char* find_func_name(paddr_t dnpc,paddr_t* address);
static int call_num = 0;//call ++, return --
static paddr_t last_function_start_address = 0;
//if is not in the same function jal clould just be a call	
void ftrace_jal(paddr_t pc,int dest,paddr_t dnpc){
	paddr_t address = 0;
	char* function_name = find_func_name(dnpc,&address); 
	if(address != last_function_start_address && address == dnpc){
	call_num ++;
	log_write("%lx:\t",pc);
	for(int i=0;i<call_num;i++){
		log_write("\t");
	}	
	log_write("call [%s@%lx]\n",function_name,address);
	}
	last_function_start_address = address;
}
//jalr may be a return
void ftrace_jalr(paddr_t pc,int dest,int rs1,word_t imm,paddr_t dnpc){
	paddr_t address = 0;
	char* function_name = find_func_name(dnpc,&address); 
	if(dest == 0 && rs1 ==1&& imm == 0){//return	
		call_num --;
		assert(call_num >=0);
		log_write("%lx:\t",pc);
		for(int i=0;i<call_num;i++){
			log_write("\t");
		}	
		log_write("ret [%s@%lx]\n",function_name,address);
		last_function_start_address = address;
	}
	else{
	if(address != last_function_start_address && address == dnpc){
	call_num ++;
	log_write("%lx:\t",pc);
	for(int i=0;i<call_num;i++){
		log_write("\t");
	}	
	log_write("call [%s@%lx]\n",function_name,address);
	}
	last_function_start_address = address;
	}
}

static char* find_func_name(paddr_t dnpc, paddr_t* address){
	for(int i=0;i < symbol_table_num;i++){
		//!here symbol_tables[i].st_info should be equal STT_FUNC but STT_FUNC == 2 ,in elf file's symbol table info==18 mean func
		if(symbol_tables[i].st_info == 18 && dnpc >= symbol_tables[i].st_value && dnpc < symbol_tables[i].st_value+symbol_tables[i].st_size){
			*address = symbol_tables[i].st_value;
			return str_table + symbol_tables[i].st_name;
		}	
	}
	assert(0);//,"jal or jalr but the dnpc is not in any func");
	return str_table;//never reach here
}
#endif
